Starvation in the event loop occurs when lower-priority tasks (like I/O callbacks or timers) are repeatedly delayed because higher-priority microtasks keep being added, preventing the event loop from ever processing the macrotask queue.
In the context of JavaScript's event loop, starvation occurs when a particular type of task is consistently unable to execute because the event loop is perpetually busy processing higher-priority tasks. Specifically, because the event loop processes all queued microtasks before moving on to the next macrotask, recursively adding microtasks can starve macrotasks (like setTimeout, I/O callbacks, and UI rendering). This is often referred to as a "microtask deadlock" or "microtask starvation".
Promise-based while loop: A common mistake is using a Promise.resolve().then() inside a loop to iterate without blocking the event loop. However, if the loop condition never becomes false, it will keep adding microtasks and starve I/O and timer events.
Unoptimized Virtual DOM Diffing: A UI framework (like React) that uses microtasks for state updates could, in theory, cause starvation if an update triggers another update synchronously, leading to an infinite microtask cycle, though modern frameworks use heuristics to prevent this.
Recursive MutationObserver callbacks: MutationObserver callbacks are microtasks. If a callback modifies the DOM in a way that triggers a new mutation, which then schedules another microtask, it can create a starvation loop, freezing the page.
To prevent starvation, you must avoid recursively adding microtasks without yielding to the event loop. If you need to process a large amount of data without blocking, you can break the work into chunks and schedule the next chunk as a macrotask using setTimeout(..., 0) or setImmediate(). This ensures that the event loop has an opportunity to process other pending tasks, such as I/O operations and timers, preventing starvation.